Описание модулей в SystemVerilog
Основой цифровых схем в SystemVerilog является модуль. Модуль — это блок SystemVerilog-кода, описывающий цифровую схему какого-то устройства, например пульта телевизора:
У пульта есть входные сигналы: кнопки, нажатие на которые сообщает о нашем намерении изменить громкость или переключить канал. Кроме того, есть выходной сигнал ИК-светодиода, по которому пульт отправляет информацию телевизору.
Для создания модуля в языке SystemVerilog используются ключевые слова module
и endmodule
, которые определяют начало и конец модуля, обрамляя его. Можно сказать, что эти ключевые слова являются корпусом нашего устройства, отделяют его содержимое от внешнего мира.
Определим наш модуль:
module
endmodule
У всякого модуля должно быть название. Назовём его box
. В круглых скобках пишутся имена портов, их направление и типы. Если модуль не имеет ни входов, ни выходов, внутри скобок ничего не пишется. После них всегда ставится точка с запятой.
module box();
endmodule
Модуль без входов и выходов (портов) — это просто коробка, которая никак не взаимодействует с внешним миром. Подключим к нему два входных сигнала a, b
и один выходной q
. Для объявления портов, необходимо указать направление порта (вход это или выход), и тип используемого сигнала. В рамках данного курса лабораторных работ в качестве типа и входов и выходов будет использоваться тип logic
, о котором будет рассказано чуть позже.
module box(
input logic a,
input logic b,
output logic q
);
endmodule
Внутри модуля могут быть объявления сигналов, параметров, констант и т.п., о которых другой модуль не узнает. Объявим внутри модуля box
провод c
.
module box(
input logic a,
input logic b,
output logic q
);
logic c;
endmodule
Для объявления провода c
использовалось ключевое слово (тип) logic
. Этот тип может в конечном итоге привести к созданию как ячеек памяти (регистров), так и проводов, в зависимости от того, как было описано присваивание объекту этого типа (подобно тому как стволовые клетки организма могут дифференцироваться в специализированные клетки в зависимости от ситуации). Поэтому в примере выше говорить о том, что был создан провод не совсем корректно, объект схемы c
станет проводом, когда будет произведено подключение к этому объекту, соответствующее подключению провода.
Подключим провод c
ко входу a
. Для этого используется конструкция assign c = a;
. Такая конструкция называется непрерывным присваиванием. Если очень сильно упростить, то непрерывное присваивание схоже со спайкой двух проводов. После подобного присваивания, провод c
всегда будет иметь то же значение, что и a
— как только входной сигнал a
изменит свое значение, внутренний провод c
также изменит свое значение (проводу c
будет непрерывно присваиваться значение входа a
).
module box(
input logic a,
input logic b,
output logic q
);
logic c;
assign c = a;
endmodule
Стоит, однако, заметить, что аналогия со спайкой проводов имеет свои недостатки: после неё некоторые студенты начинают думать, что расположение "спаиваемых" сигналов относительно знака равно не имеет значения, однако это не так.
В непрерывном присваивании участвует две компоненты: выражение-приемник сигнала и выражение-источник сигнала. Обычно, выражением-приемником является провод (либо группа проводов). Выражение-источник сигнала может быть совершенно различным. В примере, приведенном выше, выражением-источником так же был провод, но вместо него мог использоваться и регистр, и выражение, построенное из цепочки арифметических или логических вентилей.
Важно понять, что при непрерывном присваивании слева от знака равно указывается то, чему мы будем присваивать, а справа от знака равно указывается то, что мы будем присваивать.
К примеру, мы можем присвоить проводу с
значение выхода логического вентиля. Пусть нам нужно, чтобы к сигналу c
был подключен результат операции a ИЛИ b
.
Такую схему можно реализовать следующим описанием:
module box(
input logic a,
input logic b,
output logic q
);
logic c;
assign c = a | b;
endmodule
Пусть в схеме имеется ещё один логический вентиль - Исключающее ИЛИ. На него подаётся результат операции a ИЛИ b
, то есть c
, а также входной сигнал b
. Результат операции c ИСКЛЮЧАЮЩЕЕ ИЛИ b
подаётся на выход q
нашего модуля.
module box(
input logic a,
input logic b,
output logic q
);
logic c;
assign c = a | b;
assign q = c ^ b;
endmodule
Отлично! Мы научились создавать простейшее описание модуля.
Для завершения базового представления о модулях осталось разобраться с таким понятием как вектор.
Векторы
В SystemVerilog вектором называют группу проводов или регистров, объединенных общим именем, которая может использоваться как для передачи многоразрядных чисел, так и нескольких сигналов, выполняющих общую задачу.
Синтаксис объявления вектора:
<тип> [<старший индекс>:<младший индекс>] имя_вектора
Несмотря на то, что может использоваться любой диапазон индексов (даже отрицательный), на практике стараются начинать младший индекс с нуля.
Пример:
logic [7:0] sum; // Объявляется 8-битный вектор с именем sum типа logic. // Старший индекс равен 7, младший — 0.
Используя индекс, можно обратиться к отдельным битам вектора. С помощью диапазона индексов можно получить доступ к диапазону соответствующих битов.
фрагмент кода | описание |
---|---|
sum[0]; | Обращение к младшему биту вектора sum, объявленного выше |
sum[7:5]; | Обращение к старшим трём битам 8-битного вектора sum, объявленного выше |
sum[5+:3]; | Обращение к трём битам, начиная со пятого (т.е. это аналог предыдущего выражения, удобно использовать, когда известен начальный бит и их количество, а конечный нужно считать через них) |
sum[7-:3]; | Обращение к трём битам, заканчивая седьмым (т.е. это аналог предыдущего выражения, удобно использовать, когда известен конечный бит и их количество, а начальный нужно считать через них) |
Таблица 1. Способы обращения как к отдельным битам вектора, так и к диапазонам его бит.
Векторы могут быть использованы и при описании портов модуля:
module vector_ex(
input logic [3:0] a, // У данного модуля четырехразрядный вход 'a'
output logic [7:0] b // и восьмиразрядный выход 'b'.
);
assign b[7:4] = a; // К старшим четырем битам выхода b подключен вход a
assign b[3:1] = a[2:0]; // К битам с третьего по первый выхода b подключены
// биты со второго по нулевой входа a
assign b[0] = a[3]; // к младшему биту b подключен старший бит a;
endmodule
Иерархия модулей
Модули могут содержать другие модули. Реализуя модуль "Пульт ДУ" можно использовать такие цифровые схемы как "Передатчик ИК-сигнала" и "Контроллер нажатия клавиш". Обе эти цифровые схемы могут быть независимыми модулями, которые объединяются в модуле верхнего уровня.
Допустим, у нас есть модуль inv
, который подает на выход инверсию входа, и мы хотим реализовать модуль top
, который хочет использовать функционал модуля inv
следующим образом:
Опишем inv
:
module inv(
input logic a,
output logic d
);
assign d = ~a;
endmodule
Опишем модуль top
:
module top(
input logic a,
input logic b,
output logic q
);
// создаём вспомогательный провод c
logic c;
// подключение модуля
inv invertor_1( // подключаем модуль inv и
// даём экземпляру этого модуля
// имя invertor_1
.a(a), // вход а модуля inv подключаем ко
//входу a модуля top
.d(c) // выход d модуля inv подключаем к
// проводу с модуля top
);
endmodule
Обратите внимание на то, как подключаются сигналы к вложенному модулю: при подключении после .
пишется имя сигнала подключаемого модуля, затем в скобках пишется имя сигнала подключающего модуля. Для лучшего понимания, посмотрите внимательно на схеме на провод c
и выход d
модуля inv
, а также на SystemVerilog-описание этой схемы.
Мы можем подключить сколько угодно экземпляров одного модуля, поэтому у каждого из экземпляра должно быть свое уникальное имя. Пусть c
подаётся на логический вентиль И вместе со входом b
. Результат операции И тоже пойдет на инвертор, а затем на выход q
модуля top.
Тогда в нашем описании добавится подключение второго модуля inv
и провод c
.
module top(
input logic a,
input logic b,
output logic q
);
// создаём вспомогательный провод c
logic c;
// подключение модуля 1
inv invertor_1( // подключаем модуль inv и даём ему
// имя invertor_1
.a(a), // подключаем вход 'а' модуля inv ко
// входу 'a' модуля top
.d(c) // подключаем выход 'd' модуля inv к
// проводу 'с' модуля top
);
// подключение модуля 2
inv invertor_2( // подключаем модуль inv и даём ему
// имя invertor_2
.a(c & b), // на вход 'а' модуля inv подаём
// результат логической операции
// "с И b"
.d(q) // подключаем выход 'd' модуля inv
// к выходу q модуля top
);
endmodule
Итоги главы
- Ключевым блоком в иерархии цифровой схемы, описанной на языке SystemVerilog является модуль. Модули позволяют выносить части сложной цифровой схемы в отдельные блоки, из которых потом и будет составлена итоговая схема, что сильно упрощает разработку.
- Условно, модуль можно разделить на следующие части:
- Объявление модуля:
- Ключевые слова
module
/endmodule
определяющие границы описания модуля. - Название модуля, следующее за ключевым словом
module
. Описанный модуль представляет собой отдельный тип, имя которого совпадает с названием модуля. - Указание входов и выходов (портов) модуля, идущих в круглых скобках после названия модуля. Для указания направления порта модуля используются ключевые слова
input
иoutput
. После указание направления порта следует указать тип порта (в рамках данного курса типом портов всегда будетlogic
), его разрядность, а затем имя.
- Ключевые слова
- Функциональное описание модуля:
- Объявление внутренних сигналов модуля (будь то проводов или регистров) с помощью ключевого слова
logic
. - Создание при необходимости объектов других модулей.
- Описание функциональной связи между различными сигналами и объектами внутри описываемого модуля.
- Объявление внутренних сигналов модуля (будь то проводов или регистров) с помощью ключевого слова
- Объявление модуля:
Проверьте себя
Как, по-вашему, описать нижеприведенную схему на языке описания аппаратуры SystemVerilog?
Обратите внимание, что вход a
модуля top
является двухразрядным: нулевой его бит идет на вход a
модуля or
, первый бит идет на вход b
модуля or
.